﻿using System;
using System.Collections.Generic;
using System.Collections;

namespace Quicklight.Web {
   
   /// <summary>
   /// The ObservableList wraps a generic List and fires the ListChanged event if any calls are made to change the wrapped list.
   /// </summary>
   /// <typeparam name="T">The type of objects stored by the wrapped list</typeparam>
   public class ObservableList<T> : IList<T> {
      private List<T> list;

      /// <summary>
      /// Constructs an ObservableList
      /// </summary>
      public ObservableList() {
         list = new List<T>();
      }

      /// <summary>
      /// Constructs an ObservableList with the specified capacity
      /// </summary>
      /// <param name="capacity">Capacity of the wrapped list</param>
      public ObservableList(int capacity) {
         list = new List<T>(capacity);
      }

      /// <summary>
      /// Constructs an ObservableList using the specified array of items to populate the wrapped list
      /// </summary>
      /// <param name="items">Array of items to use in populating the wrapped list</param>
      public ObservableList(T[] items) {
         list = new List<T>(items);
      }

      /// <summary>
      /// Constructs an ObservableList using the supplied items
      /// </summary>
      /// <param name="items">Items to use in populating the wrapped list</param>
      public ObservableList(IEnumerable<T> items) {
         list = new List<T>(items);
      }

      /// <summary>
      /// Constructs an ObservableList using the specified list of items to populate the wrapped list
      /// </summary>
      /// <param name="items">List of items to use in populating the wrapped list</param>
      public ObservableList(List<T> items) {
         list = new List<T>(items);
      }

      /// <summary>
      /// Event that fires when a call is made to change the wrapped list
      /// </summary>
      public event EventHandler<ListChangedEventArgs<T>> ListChanged;

      /// <summary>
      /// Returns the count of items in the wrapped list
      /// </summary>
      public int Count {
         get { return list.Count; }
      }

      /// <summary>
      /// Gets and sets the item in the wrapped list at the specified index
      /// </summary>
      /// <param name="index"></param>
      /// <returns></returns>
      public T this[int index] {
         get { return list[index]; }
         set {
            T removeItem = list[index];
            list[index] = value;
            OnCollectionChanged(index, new List<T>(value), index, new List<T>(removeItem));
         }
      }

      /// <summary>
      /// Adds the specified item to the wrapped list
      /// </summary>
      /// <param name="item">Item to add to the wrapped list</param>
      public void Add(T item) {
         list.Add(item);
         OnCollectionChanged(list.Count, new List<T>(item), 0, new List<T>());
      }

      /// <summary>
      /// Adds items to the wrapped list
      /// </summary>
      /// <param name="items">Items to add to the wrapped list</param>
      public void AddRange(IEnumerable<T> items) {
         list.AddRange(items);
         OnCollectionChanged(list.Count, new List<T>(items), 0, new List<T>());
      }

      /// <summary>
      /// Clear all items from the wrapped list
      /// </summary>
      public void Clear() {
         List<T> removeList = new List<T>(list);
         list.Clear();
         OnCollectionChanged(0, new List<T>(), 0, removeList);
      }

      /// <summary>
      /// Clones the wrapped list
      /// </summary>
      /// <returns>A cloned copy of the wrapped list</returns>
      public List<T> Clone() {
         return list.Clone();
      }

      /// <summary>
      /// Concatenates the supplied objects to the wrapped list and return the resulting list
      /// </summary>
      /// <param name="objects">Objects to concatenate to the wrapped list</param>
      /// <returns>A list that contains the items in the wrapped list and the specified objects concatenated</returns>
      public List<T> Concat(params T[] objects) {
         return list.Concat(objects);
      }

      /// <summary>
      /// Tests if the specified item is in the wrapped list
      /// </summary>
      /// <param name="item">Item to search for in the wrapped list</param>
      /// <returns>True if specified object is in list, false otherwise</returns>
      public bool Contains(T item) {
         return list.Contains(item);
      }

      /// <summary>
      /// Applies the callback delegate to each item of the wrapped list and returns true if the callback returned true on all items
      /// </summary>
      /// <param name="callback">Delegate that returns true if an item conforms to a condition</param>
      /// <returns>Returns true if the callback returned true on all items, false otherwise</returns>
      public bool Every(Func<T, bool> callback) {
         return list.Every(callback);
      }

      /// <summary>
      /// Applies the callback delegate to each item of the wrapped list and returns true if the callback returned true on all items
      /// </summary>
      /// <param name="callback">Delegate that returns true if an item conforms to a condition</param>
      /// <returns>Returns true if the callback returned true on all items, false otherwise</returns>
      public bool Every(Func<T, int, List<T>, bool> callback) {
         return list.Every(callback);
      }

      /// <summary>
      /// Return a sub list from the wrapped list starting at the specified index
      /// </summary>
      /// <param name="index">Index in wrapped list to start sub list from</param>
      /// <returns>Sub list which is created from wrapped list starting at specified index</returns>
      public List<T> Extract(int index) {
         return list.Extract(index);
      }

      /// <summary>
      /// Return a sub list from the wrapped list starting at the specified index and with the specified length
      /// </summary>
      /// <param name="index">Index in wrapped list to start sub list from</param>
      /// <param name="count">Length of the sub list to extract from the wrapped list</param>
      /// <returns>Sub list which is created from wrapped list starting at specified index with the specified length</returns>
      public List<T> Extract(int index, int count) {
         return list.Extract(index, count);
      }

      /// <summary>
      /// Filter items from the wrapped list that make the callback return true when applied to the item
      /// </summary>
      /// <param name="callback">Callback delegate to apply to each item to determine if it should form part of the returned list</param>
      /// <returns>List of filtered items that all made the callback return true when applied to the item</returns>
      public List<T> Filter(Func<T, bool> callback) {
         return list.Filter(callback);
      }

      /// <summary>
      /// Filter items from the wrapped list that make the callback return true when applied to the item
      /// </summary>
      /// <param name="callback">Callback delegate to apply to each item to determine if it should form part of the returned list</param>
      /// <returns>List of filtered items that all made the callback return true when applied to the item</returns>
      public List<T> Filter(Func<T, int, List<T>, bool> callback) {
         return list.Filter(callback);
      }

      /// <summary>
      /// Apply the callback function to each item in the wrapped list
      /// </summary>
      /// <param name="callback">Callback delegate to apply to each item in the wrapped list</param>
      public void ForEach(Action<T> callback) {
         list.ForEach(callback);
      }

      /// <summary>
      /// Explicit interface implementation of GetEnumerator on IEnumerable interface. 
      /// </summary>
      /// <returns>An IEnumerator of the wrapped list</returns>
      IEnumerator IEnumerable.GetEnumerator() {
         return list.GetEnumerator();
      }

      /// <summary>
      /// Returns the generic IEnumerator of the wrapped list
      /// </summary>
      /// <returns>The generic IEnumerator of the wrapped list</returns>
      public IEnumerator<T> GetEnumerator() {
         return list.GetEnumerator();
      }

      /// <summary>
      /// Finds the index of the specified item in the wrapped list
      /// </summary>
      /// <param name="item">Item to find the index of in the wrapped list</param>
      /// <returns>Index of the item in the wrapped list</returns>
      public int IndexOf(T item) {
         return list.IndexOf(item);
      }

      /// <summary>
      /// Finds the index of the specified item in the wrapped list, starting at the startIndex specified
      /// </summary>
      /// <param name="item">Item to find the index of in the wrapped list</param>
      /// <param name="startIndex">Index to start looking at for the specified item</param>
      /// <returns>Index of the item in the wrapped list</returns>
      public int IndexOf(T item, int startIndex) {
         return list.IndexOf(item, startIndex);
      }

      /// <summary>
      /// Inserts the specified item at the specified index
      /// </summary>
      /// <param name="index">Index in the wrapped list to insert the specified item</param>
      /// <param name="item">Item to insert into the wrapped list at the specified index</param>
      public void Insert(int index, T item) {
         list.Insert(index, item);
         OnCollectionChanged(index, new List<T>(item), 0, new List<T>());
      }

      /// <summary>
      /// Insert range of items into the wrapped list, starting at the index specified
      /// </summary>
      /// <param name="index">Index in the wrapped list to insert the specified range of items</param>
      /// <param name="item">Items to insert into the wrapped list at the specified index</param>
      public void InsertRange(int index, IEnumerable<T> items) {
         list.InsertRange(index, items);
         OnCollectionChanged(index, new List<T>(items), 0, new List<T>());
      }

      /// <summary>
      /// Returns a string of the items in the wrapped list joined together
      /// </summary>
      /// <returns>String of the items in the list joined together</returns>
      public string Join() {
         return list.Join();
      }

      /// <summary>
      /// Returns a string of the items in the wrapped list joined together using the delimeter
      /// </summary>
      /// <param name="delimiter">Delimeter to use in seperating the items in the string returned</param>
      /// <returns>String of the items in the list joined together using the delimeter</returns>
      public string Join(string delimiter) {
         return list.Join(delimiter);
      }

      /// <summary>
      /// Applies the callback delegate to each item and places the return of the callback in the returned list
      /// </summary>
      /// <typeparam name="TTarget">Type of the items in the returned list</typeparam>
      /// <param name="callback">Delegate to map each item in the wrapped list to an item in the returned list</param>
      /// <returns>List of items generated from applying the callback delegate to each item</returns>
      public List<TTarget> Map<TTarget>(Func<T, TTarget> callback) {
         return list.Map(callback);
      }

      /// <summary>
      /// Applies the callback delegate to each item and places the return of the callback in the returned list
      /// </summary>
      /// <typeparam name="TTarget">Type of the items in the returned list</typeparam>
      /// <param name="callback">Delegate to map each item in the wrapped list to an item in the returned list</param>
      /// <returns>List of items generated from applying the callback delegate to each item</returns>
      public List<TTarget> Map<TTarget>(Func<T, int, List<T>, TTarget> callback) {
         return list.Map(callback);
      }

      /// <summary>
      /// Removes the specified item from the wrapped list and returns true if it was found and removed
      /// </summary>
      /// <param name="item">Item to remove from the wrapped list</param>
      /// <returns>True if the item was removed, false otherwise</returns>
      public bool Remove(T item) {
         int index = list.IndexOf(item);
         bool result = list.Remove(item);
         OnCollectionChanged(0, new List<T>(), index, new List<T>(item));
         return result;
      }

      /// <summary>
      /// Removes the item at the specified index in the wrapped list
      /// </summary>
      /// <param name="index">Index in the wrapped list at which the item is removed</param>
      public void RemoveAt(int index) {
         T item = list[index];
         list.RemoveAt(index);
         OnCollectionChanged(0, new List<T>(), index, new List<T>(item));
      }

      /// <summary>
      /// Removes a range of items from the wrapped list, starting at the specifeid index and for the length of count
      /// </summary>
      /// <param name="index">Index in  wrapped list to start removing items</param>
      /// <param name="count">Length of items to remove from the wrapped list</param>
      public void RemoveRange(int index, int count) {
         List<T> removeItems = list.Extract(index, count);
         list.RemoveRange(index, count);
         OnCollectionChanged(0, new List<T>(), index, removeItems);
      }

      /// <summary>
      /// Reverses the order of items in the wrapped list
      /// </summary>
      public void Reverse() {
         List<T> removeItems = new List<T>(list);
         list.Reverse();
         OnCollectionChanged(0, new List<T>(list), 0, removeItems);
      }

      /// <summary>
      /// Return an array from the wrapped list starting at the specified index
      /// </summary>
      /// <param name="index">Index in wrapped list to start array from</param>
      /// <returns>Array which is created from wrapped list starting at specified index</returns>
      public Array Slice(int index) {
         return list.Slice(index);
      }

      /// <summary>
      /// Return an array from the wrapped list starting at the specified index and with the specified length
      /// </summary>
      /// <param name="index">Index in wrapped list to start array from</param>
      /// <param name="count">Length of the array to extract from the wrapped list</param>
      /// <returns>Array which is created from wrapped list starting at specified index with the specified length</returns>
      public Array Slice(int index, int count) {
         return list.Slice(index, count);
      }

      /// <summary>
      /// Applies the callback delegate to each item of the wrapped list and returns true if at least one callback returned true
      /// </summary>
      /// <param name="callback">Delegate that returns true if an item conforms to a condition</param>
      /// <returns>Returns true if at least one callback returned true, false otherwise</returns>
      public bool Some(Func<T, bool> callback) {
         return list.Some(callback);
      }

      /// <summary>
      /// Applies the callback delegate to each item of the wrapped list and returns true if at least one callback returned true
      /// </summary>
      /// <param name="callback">Delegate that returns true if an item conforms to a condition</param>
      /// <returns>Returns true if at least one callback returned true, false otherwise</returns>
      public bool Some(Func<T, int, List<T>, bool> callback) {
         return list.Some(callback);
      }

      /// <summary>
      /// Sort the items in the wrapped list
      /// </summary>
      public void Sort() {
         List<T> removeItems = new List<T>(list);
         list.Sort();
         OnCollectionChanged(0, new List<T>(list), 0, removeItems);
      }

      /// <summary>
      /// Sort the items in the wrapped list using the callback delegate specified
      /// </summary>
      /// <param name="callback">Delegate to use in determining the sort order of the items in the wrapped list</param>
      public void Sort(Func<T, T, int> callback) {
         List<T> removeItems = new List<T>(list);
         list.Sort(callback);
         OnCollectionChanged(0, new List<T>(list), 0, removeItems);
      }

      /// <summary>
      /// Returns a comma delimited string represenation of the content of the wrapped list
      /// </summary>
      /// <returns>Result of calling ToLocaleString on the wrapped list</returns>
      public override string ToLocaleString() {
         return list.ToLocaleString();
      }

      /// <summary>
      /// Returns a comma delimited string represenation of the content of the wrapped list. 
      /// When binding the ObservableList to a view, this method is used to obtain a string value for the binding
      /// Override to customize the string returned to the view
      /// </summary>
      /// <returns>Result of calling ToString on the wrapped list</returns>
      public override string ToString() {
         return list.ToString();
      }

      /// <summary>
      /// Fires the CollectionChanged event if there are subscribers
      /// </summary>
      /// <param name="insertIndex">Index at which inserts, if any, start</param>
      /// <param name="insertList">List of items that were inserted</param>
      /// <param name="removeIndex">Index at which removals, if any, start</param>
      /// <param name="removeList">List of items that were removed</param>
      private void OnCollectionChanged(int insertIndex, List<T> insertItems, int removeIndex, List<T> removeItems) {
         if (ListChanged != null)
            ListChanged(this, new ListChangedEventArgs<T>(insertIndex, insertItems, removeIndex, removeItems));
      }
   }
}
